/* eslint-disable camelcase */
import Connection from './Connection';
import RequestQueue from './RequestQueue';
import { ConnectError, ResponseError, ErrorCode } from '../errors';
import { toObject, NumberIDGenerator, debounce } from '../utils';
import { ProtocolEvent, ErrorEvents, ConnectionEvent } from '../constants';

/**
 * 事件规范
 * 事件统一用常量管理如：
 *  ProtocolEvent, ErrorEvents, ConnectionEvent
 * 错误事件统一用 fireXXXXX 方法触发如：
 * fireProtocolError, fireConnectError
 * 切勿直接用 $emit 触发错误事件，避免重复触发
 */

/**
 * 上下行消息的 cmd
 * 目前同类消息上下行 cmd 都一致
 * 所以无需区分同一 cmd 的上下行值
 */
const COMMANDS = {
  BEATS: 'ping',
  AUTH: 'auth',
};

export const CONN_STATUS = {
  FAILED: -1,
  DEFAULT: 0,
  CONNECTED: 1,
  USER_BIND: 2,
  OK: 3,
};

export default class Protocol extends Connection {
  conn_status = CONN_STATUS.DEFAULT;

  beatsTimer = 0;

  handshakeExpireTime = null;

  userToken = null;

  beatTimoutTimes = 0;

  reconnectTimes = 0;

  requestQueue = new RequestQueue();

  userId = null;

  bizUserType = null;

  bizUserKey = null;

  timeoutObj = null;

  serverTimeoutObj = null;

  heartCheck = {
    reset: () => {
      clearTimeout(this.timeoutObj);
      clearTimeout(this.serverTimeoutObj);
      return this.heartCheck;
    },
    start: () => {
      return new Promise((resolve, reject) => {
        console.log('心跳开始');
        this.timeoutObj = setTimeout(() => {
          this.sendMsg(COMMANDS.BEATS, {});
          resolve();
          this.serverTimeoutObj = setTimeout(async () => {
            // 如果超过一定时间还没重置，说明后端主动断开了
            this.setConnectionStatus(CONN_STATUS.FAILED);
            this.connectFailed();
            await this.reconnect();
          }, this.configs.maxHeartbeatDuration);
        }, this.configs.maxHeartbeatDuration);
      });
    },
  };

  /**
   * 各种事件处理器
   */
  processors = {
    send: this.listenMesage,
    pong: this.handlerBeats,
    auth: this.authChecked,
    push: this.handlerPushMsg,
  };

  constructor(configs) {
    super(configs);
    this.configs = configs;
    this.listen();
  }

  /**
   * 通用处理器
   * @param message{string}
   */
  handler(message) {
    const objMessage = toObject(message);
    const { cmd } = toObject(message);
    const processor = this.processors[cmd];
    if (processor) {
      processor.call(this, objMessage);
    } else {
      console.warn('事件被忽略', message);
    }
  }

  /**
   * 重连
   */
  async reconnect() {
    if (this.reconnectTimes >= this.configs.maxReconnectTimes) {
      console.warn(this.configs.maxReconnectTimes, this.reconnectTimes, '进入失败重联');
      this.setConnectionStatus(CONN_STATUS.FAILED);
      // 触发连接失败错误，移交重连决定权
      this.fireConnectError(new ConnectError(this.conn_status, this.reconnectTimes, true));
      this.reconnectTimes = 0;
    } else {
      this.reconnectTimes += 1;
      this.setConnectionStatus(CONN_STATUS.DEFAULT);
      this.close();
      // 不需要重试，reconnect 主动重试
      await this.connect(this.bizUserType, this.bizUserKey, false);
      if (this.conn_status === CONN_STATUS.OK) {
        // 重连成功
        this.reconnectTimes = 0;
      } else {
        this.fireConnectError(new ConnectError(this.conn_status, this.reconnectTimes));
        await this.reconnect();
      }
    }
  }

  /**
   * 发送消息
   */
  sendMessage(cmd, data) {
    this.sendMsg(cmd, data);
  }

  /**
   * 发起鉴权操作
   */
  async authCheck() {
    const data = {
      'X-Auth-Token': this.bizUserKey,
      systemUri: this.configs.systemUri,
      'X-Preference': this.configs.xPreference,
    };
    await this.sendMsg(COMMANDS.AUTH, data);
  }

  /**
   * 鉴权完成操作
   */
  async authChecked(message) {
    console.log('message.....................................', message);
    if (message.code === '0') {
      this.setConnectionStatus(CONN_STATUS.USER_BIND);
      console.log(this.conn_status, '用户绑定成功');
      this.heartCheck.reset().start();
    } else {
      this.close();
      this.setConnectionStatus(CONN_STATUS.FAILED);
      this.connectFailed();
      await this.reconnect();
    }
  }

  /**
   *
   * @param {*} cmd
   * @param {*} data
   */
  async handlerBeats(message) {
    if (message.code === '0') {
      this.beatTimoutTimes = 0;
      this.heartCheck.reset().start();
      this.setConnectionStatus(CONN_STATUS.OK);
    } else {
      this.close();
      this.setConnectionStatus(CONN_STATUS.FAILED);
      this.connectFailed();
      await this.reconnect();
    }
  }

  handlerPushMsg(message) {
    this.$emit(ProtocolEvent.RECEIVE_MESSAGE, message);
    const { id, msgId } = message;
    this.sendMsg('ack', { id, msgId });
  }

  async sendMsg(cmd, data) {
    const request = { cmd, ...data };
    super.send(JSON.stringify(request));
  }

  /**
   * 监听当层行为
   */
  listen() {
    super.onMessage(this.handler.bind(this));
    super.onError((e) => {
      this.$emit(ConnectionEvent.ERROR, e);
      this.setConnectionStatus(CONN_STATUS.FAILED);
      this.fireProtocolError(new ConnectError(this.conn_status, this.reconnectTimes));
    });
    super.onClose((e) => {
      this.$emit(ConnectionEvent.CLOSED, e);
      this.setConnectionStatus(CONN_STATUS.FAILED);
      this.fireProtocolError(new ConnectError(this.conn_status, this.reconnectTimes));
      this.close();
    });
    super.onOpen((e) => {
      this.$emit(ConnectionEvent.OPENED, e);
      // pass
    });
  }

  /**
   * 协议层连接
   * @return {Promise<void>}
   */
  async connect(bizUserType = this.bizUserType, bizUserKey = this.bizUserKey, retry = true) {
    this.bizUserType = bizUserType;
    this.bizUserKey = bizUserKey;
    try {
      // todo debounce
      // 如果已经连接上则不重复连接
      if (this.conn_status === CONN_STATUS.OK) {
        return true;
      }
      this.setConnectionStatus(CONN_STATUS.DEFAULT);
      const { url, isAuth } = this.configs;
      // 1. 底层连接
      await super.connect(url);
      this.setConnectionStatus(CONN_STATUS.CONNECTED);
      console.log(this.conn_status, 'connect连接完成');
      // 2. 发起鉴权
      if (isAuth) {
        await this.authCheck();
      } else {
        await this.heartCheck.reset().start();
      }

      this.setConnectionStatus(CONN_STATUS.USER_BIND);
      this.ready();
    } catch (e) {
      this.close();
      this.setConnectionStatus(CONN_STATUS.FAILED);
      this.connectFailed();
      if (retry) await this.reconnect();
    }
  }

  /**
   * 断开连接
   */
  close() {
    super.close();
  }

  /**
   * 触发报错事件
   * @param error
   */
  fireProtocolError(error) {
    this.$emit(ErrorEvents.PROTOCOL_ERROR, error);
  }

  /**
   * 触发连接错误事件
   * @param error
   */
  fireConnectError(error) {
    this.$emit(ErrorEvents.CONNECTION_ERROR, error);
  }

  /**
   * 监听服务端收到消息
   * @param fn
   */
  onReceiveMessage(fn) {
    this.$on(ProtocolEvent.RECEIVE_MESSAGE, fn);
  }

  /**
   * 一切就绪
   */
  ready() {
    this.$emit(ProtocolEvent.CONN_ESTABLISHED);
  }

  /**
   * 连接失败
   */
  connectFailed() {
    const error = new ConnectError(this.conn_status, this.reconnectTimes);
    this.fireProtocolError(error);
    this.fireConnectError(error);
    this.close();
  }

  setConnectionStatus(status) {
    if (status !== this.conn_status) {
      this.conn_status = status;
      this.$emit(ProtocolEvent.CONN_STATE_CHANGE, status);
    }
  }

  onConnect() {
    this.$on(ProtocolEvent.CONN_ESTABLISHED);
  }

  /**
   * 一切就绪
   * 必要性带考虑
   * @return {Promise<void>}
   */
  async onReady() {
    // 连接已经失败
    if (this.conn_status === CONN_STATUS.FAILED) {
      throw new ConnectError(this.conn_status, this.reconnectTimes);
    }
    if (this.conn_status === CONN_STATUS.OK) {
      return;
    }
    console.log(`<-------------------连接完成 ${this.conn_status}---------------------->`);
    // 对连接进行监听连接成功后做出响应
    await new Promise((resolve, reject) => {
      // 1. 设定超时计时器
      const countDownTimer = setTimeout(() => {
        reject();
      }, 10000);

      // 成功时卸载监听方法
      const success = () => {
        console.log('成功回调。。。。');
        this.setConnectionStatus(CONN_STATUS.OK);
        this.$off(ErrorEvents.CONN_FAILED, failed);
        clearTimeout(countDownTimer);
        resolve();
      };
      // 失败时卸载监听方法
      const failed = () => {
        // eslint-disable-next-line no-debugger
        this.setConnectionStatus(CONN_STATUS.FAILED);
        this.$off(ErrorEvents.CONN_ESTABLISHED, success);
        clearTimeout(countDownTimer);
        reject();
      };
      // 2. 监听连接建立
      this.$on(ProtocolEvent.CONN_ESTABLISHED, success, null, true);
      // 3. 监听连接失败
      this.$on(ErrorEvents.CONN_FAILED, failed, null, true);
    });
  }
}
